from enum import Enum
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import QTextCursor, QIcon, QCloseEvent
import kernal


class Mode(Enum):
    STOP = 0
    RUNNING = 1
    TRACING = 2


def read_from(filename):
    file = QFile(filename)
    file.open(QIODevice.ReadOnly | QIODevice.Text)
    stream = QTextStream(file)
    s = stream.readAll()
    file.close()
    return s


def write_to(filename, text):
    file = QFile(filename)
    file.open(QIODevice.WriteOnly | QIODevice.Text)
    stream = QTextStream(file)
    stream << text
    stream.flush()
    file.close()


class UiManager(QObject):
    def __init__(self):
        super().__init__()

        self.ker = kernal.Kernal()
        self.currentMode = Mode.STOP
        self.currentFilename = None

        self.ui = QUiLoader().load('ui/main.ui')

        self.ui.menuFileNew.triggered.connect(self.when_new)
        self.ui.menuFileOpen.triggered.connect(self.when_open)
        self.ui.menuFileSave.triggered.connect(self.when_save)
        self.ui.menuFileSaveas.triggered.connect(self.when_saveas)
        self.ui.menuRunAnalyze.triggered.connect(self.when_analyze)
        self.ui.menuRunForwRun.triggered.connect(self.when_forw_run)
        self.ui.menuRunBackwRun.triggered.connect(self.when_backw_run)
        self.ui.menuRunForwStep.triggered.connect(self.when_forw_step)
        self.ui.menuRunBackwStep.triggered.connect(self.when_backw_step)
        self.ui.menuRunForwTo.triggered.connect(self.when_forw_to)
        self.ui.menuRunBackwTo.triggered.connect(self.when_backw_to)
        self.ui.menuRunPause.triggered.connect(self.when_pause)
        self.ui.menuRunStop.triggered.connect(self.when_stop)

        self.ui.memField.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.ui.setWindowIcon(QIcon('icon/icon.ico'))
        self.update_title()

        self.ui.show()

    def eventFilter(self, obj, event):
        if obj == self.ui and type(event) == QCloseEvent:
            if self.maybe_save():
                event.accept()
            else:
                event.ignore()
            return True
        else:
            return super().eventFilter(obj, event)

    def update_title(self):
        if self.currentFilename:
            self.ui.setWindowTitle(self.currentFilename + ' - BrainfuckIDE')
        else:
            self.ui.setWindowTitle('BrainfuckIDE')

    def update_cursor(self):
        cursor = self.ui.codeField.textCursor()
        cursor.setPosition(min(self.ker.codePointer, len(self.ker.code) - 1))
        cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor)
        selection = QTextEdit.ExtraSelection()
        selection.cursor = cursor
        selection.format.setBackground(Qt.lightGray)
        self.ui.codeField.setExtraSelections([selection])
        cursor.clearSelection()
        self.ui.codeField.setTextCursor(cursor)

    def update_output(self):
        self.ui.outputField.setPlainText(self.ker.output)
        self.ui.outputField.moveCursor(QTextCursor.End)

    def update_memory(self):
        pos = self.ker.dataPointer
        while pos >= self.ui.memField.rowCount():
            self.ui.memField.insertRow(pos)
            self.ui.memField.setItem(pos, 0, QTableWidgetItem(str(pos)))
            self.ui.memField.setItem(pos, 1, QTableWidgetItem(str(self.ker.mem[pos])))
        self.ui.memField.item(pos, 1).setText(str(self.ker.mem[pos]))
        for r in self.ui.memField.selectedRanges():
            self.ui.memField.setRangeSelected(r,    False)
        self.ui.memField.setRangeSelected(QTableWidgetSelectionRange(pos, 0, pos, 1), True)
        self.ui.memField.scrollToItem(self.ui.memField.item(pos, 0))

    def lock_panel(self):
        self.ui.codeField.setReadOnly(True)
        self.ui.inputField.setReadOnly(True)

    def unlock_panel(self):
        self.ui.codeField.setReadOnly(False)
        self.ui.inputField.setReadOnly(False)

    def maybe_save(self):
        if self.ui.codeField.document().isModified():
            reply = QMessageBox.question(
                self.ui, '提示', '是否保存当前文件？',
                QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
                QMessageBox.Save)
            if reply == QMessageBox.Discard or reply == QMessageBox.Save and self.when_save():
                return True
            else:
                return False
        return True

    def clear_all(self):
        self.ui.codeField.clear()
        self.ui.inputField.clear()
        self.ui.outputField.clear()
        self.ui.memField.clearContents()
        self.ui.memField.setRowCount(0)

    def when_start(self):
        self.lock_panel()
        self.ker.load(self.ui.codeField.toPlainText(), self.ui.inputField.toPlainText())
        self.ker.analyze()
        self.ker.start()
        self.ui.memField.clearContents()
        self.ui.memField.setRowCount(0)
        self.update_memory()

    def when_new(self):
        if self.maybe_save():
            self.when_stop()
            self.clear_all()
            self.currentFilename = None
            self.update_title()
            self.ui.codeField.clear()
            self.ui.codeField.document().setModified(False)
            return True
        return False

    def when_open(self):
        if self.maybe_save():
            filename = QFileDialog.getOpenFileName(self.ui)[0]
            if not filename:
                return False
            self.when_stop()
            self.clear_all()
            self.currentFilename = filename
            self.update_title()
            self.ui.codeField.setPlainText(read_from(filename))
            self.ui.codeField.document().setModified(False)
            return True
        return False

    def when_save(self):
        if not self.currentFilename:
            return self.when_saveas()
        write_to(self.currentFilename, self.ui.codeField.toPlainText())
        self.ui.codeField.document().setModified(False)
        return True

    def when_saveas(self):
        filename = QFileDialog.getSaveFileName(self.ui)[0]
        if not filename:
            return False
        self.currentFilename = filename
        self.update_title()
        write_to(filename, self.ui.codeField.toPlainText())
        self.ui.codeField.document().setModified(False)
        return True

    def when_analyze(self):
        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return
        try:
            self.ker.load(self.ui.codeField.toPlainText(), self.ui.inputField.toPlainText())
            self.ker.analyze()
        except AssertionError:
            QMessageBox.warning(self.ui, '语法检查', '语法错误')
        else:
            QMessageBox.information(self.ui, '语法检查', '语法正确')

    def when_forw_run(self):
        def run_():
            if self.currentMode == Mode.RUNNING and self.ker.step_forw():
                self.update_cursor()
                self.update_output()
                self.update_memory()
            else:
                timer.stop()

        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return

        if self.currentMode != Mode.STOP and self.currentMode != Mode.TRACING:
            return
        if self.currentMode == Mode.STOP:
            self.when_start()
        self.currentMode = Mode.RUNNING

        timer = QTimer()
        timer.timeout.connect(run_)
        timer.start(0)

    def when_backw_run(self):
        def run_():
            if self.currentMode == Mode.RUNNING and self.ker.step_backw():
                self.update_cursor()
                self.update_output()
                self.update_memory()
            else:
                timer.stop()

        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return

        if self.currentMode != Mode.STOP and self.currentMode != Mode.TRACING:
            return
        if self.currentMode == Mode.STOP:
            self.when_start()
        self.currentMode = Mode.RUNNING

        timer = QTimer()
        timer.timeout.connect(run_)
        timer.start(0)

    def when_forw_step(self):
        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return

        if self.currentMode != Mode.STOP and self.currentMode != Mode.TRACING:
            return

        if self.currentMode == Mode.STOP:
            self.when_start()
            self.currentMode = Mode.TRACING

        self.ker.step_forw()
        self.update_cursor()
        self.update_output()
        self.update_memory()

    def when_backw_step(self):
        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return

        if self.currentMode != Mode.TRACING:
            return

        self.ker.step_backw()
        self.update_cursor()
        self.update_output()
        self.update_memory()

    def when_forw_to(self):
        def run_():
            if self.currentMode == Mode.RUNNING and self.ker.codePointer != pos and self.ker.step_forw():
                self.update_cursor()
                self.update_output()
                self.update_memory()
            else:
                self.currentMode = Mode.TRACING
                timer.stop()

        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return

        if self.currentMode != Mode.STOP and self.currentMode != Mode.TRACING:
            return
        if self.currentMode == Mode.STOP:
            self.when_start()
        self.currentMode = Mode.RUNNING

        pos = self.ui.codeField.textCursor().position()

        timer = QTimer()
        timer.timeout.connect(run_)
        timer.start(0)

    def when_backw_to(self):
        def run_():
            if self.currentMode == Mode.RUNNING and self.ker.codePointer != pos and self.ker.step_backw():
                self.update_cursor()
                self.update_output()
                self.update_memory()
            else:
                self.currentMode = Mode.TRACING
                timer.stop()

        if self.ui.codeField.toPlainText() == '':
            QMessageBox.warning(self.ui, '提示', '代码不能为空')
            return

        if self.currentMode != Mode.TRACING:
            return
        self.currentMode = Mode.RUNNING

        pos = self.ui.codeField.textCursor().position()

        timer = QTimer()
        timer.timeout.connect(run_)
        timer.start(0)

    def when_pause(self):
        if self.currentMode != Mode.RUNNING:
            return
        self.currentMode = Mode.TRACING

    def when_stop(self):
        self.currentMode = Mode.STOP
        self.ui.codeField.setExtraSelections([])
        self.unlock_panel()
        for r in self.ui.memField.selectedRanges():
            self.ui.memField.setRangeSelected(r, False)